package util;

import java.util.*;
import java.io.*;
import java.lang.reflect.Method;
import java.lang.reflect.Constructor;

import antlr.collections.AST;

import JavaGrammar.*;

//////////////////////////////////////////////////////////////////////////
//// Util
/**
@author Ivan Jeukens
@version v 0.1 03/10/2001
*/
public class Util {

    ///////////////////////////////////////////////////////////////////
    ////                         public methods                    ////

    // public static LinkedList imports;

    // Retira os . e sustitui por / em um string. Usada para setar o
    // nome de um effigy
    public static String removeDot(String name) {
        StringTokenizer st = new StringTokenizer(name, ".");
        StringBuffer res = new StringBuffer();
        while(st.hasMoreTokens()) {
            String tok = st.nextToken();
            res.append(tok + "/");
        }
        return res.toString();
    }
    
    /*recebe o nome package.classe.classinterna e converte para 
    para package.classe$classeinterna */
    public static String innerClassLoader(String name) {
        StringTokenizer st = new StringTokenizer(name, ".");
        StringBuffer res = new StringBuffer();
        int size = st.countTokens();
        for(int i = 0;i < size - 2;i++) {
            res.append(st.nextToken() + ".");
        }
        res.append(st.nextToken() + "$" + st.nextToken());
        return res.toString();
    }
    
    /* transforma em classe.innerclasse a partir de classe$innerclasse */
    public static String innerClassName(String loader) {
        StringTokenizer st = new StringTokenizer(loader, "$");
        if(st.countTokens() <= 1) return loader;
        
        if(st.countTokens() > 2) {
            System.out.println("!!!! Util.innerClassName -> countTokens > 2");
            return loader;   
        }        
        return new String(st.nextToken() + "." + st.nextToken());
    }

    /* Duplica uma AST, criando nos do tipo LinkedAST, devidamente ligados */
    public static LinkedAST dupTree(AST tree) {
        LinkedAST ret = new LinkedAST();
        
        ret.setType(tree.getType());
        ret.setText(tree.getText());
        ret.setParent(null);

        LinkedAST sub = _copySubTree(tree.getFirstChild());
        if(sub != null) {
            ret.setFirstChild(sub);
            sub.setParent(ret);
        }
        return ret;
    }

    public static Method getMethod(Class c, String methodName,
            LinkedList parameters) {
        // Tenta carregar o metodo da classe considerando os parametros
        // exatos
        boolean loaded = false;
        Method method = null;
        try {
            method = c.getMethod(methodName, (Class[])parameters.toArray());
            loaded = true;
        }
        catch(Exception ex) {
        }
        
        if(loaded) return method;

        // Tenta carregar o metodo da classe e seus pais, considerando os
        // parametros exatos
        try {
            method = c.getDeclaredMethod(methodName,
                (Class[]) parameters.toArray());
            loaded = true;
        }
        catch(Exception ex) {
        }

        if(loaded) return method;

        // TEM QUE SER IMPLEMENTADO!
        // Considera o fato de ter um cast. Entao, a classe c pode
        // ser uma interface. Tem que procurar nas classes que a implementam
        // para ver se ha o metodo.
        if(c.isInterface()) {
            // Acoxambrando....casos reais de atores.....
            
            if(c.getName().equals("ptolemy.data.type.Typeable")) {
                try {
                    ClassLoader loader = ClassLoader.getSystemClassLoader();
                    Class t = loader.loadClass("ptolemy.actor.TypedIOPort");

                    Method m = Util.getMethod(t, methodName, parameters);
                    if(m != null) {
                        return m;
                    }
                }
                catch(ClassNotFoundException ex) {
                    System.out.println(ex);
                }
            }

        }
        
        LinkedList convertMethods = new LinkedList();
        boolean wideConvert = false;
        Class t = c;
        while(t != null) {
            if(!loaded) {
                Method [] methods = t.getDeclaredMethods();
                int i = 0;
                for(;i < methods.length;i++) {
                    Class [] methodPars = methods[i].getParameterTypes();
                    if(!methods[i].getName().equals(methodName)) continue;
                    if(methodPars.length != parameters.size()) continue;
 
                    int j = 0;
                    for(;j < methodPars.length;j++) {
                        Class pa = (Class) parameters.get(j);
                        String pna = pa.getName();
                        if(!methodPars[j].getName().equals(pna)
                              && !Util.isSubClass(pa, methodPars[j])
                              && !Util.Implements(pa, methodPars[j])
                              && pa != void.class) {                                                                                    

                            if(Util.canWideConvert(pa, methodPars[j])) {
                                wideConvert = true;
                            }
                            else {
                                break;
                            }
                        }
                    }

                    if(j == methodPars.length) {
                        if(wideConvert == false) {
                            loaded = true;
                            break;
                        }
                        else {
                            convertMethods.add(methods[i]);
                        }
                        wideConvert = false;
                    }
                }
                if(loaded) method = methods[i];
            }
            t = t.getSuperclass();
        }

        if(method == null) {
            if(convertMethods.size() > 0) {
                int conv = Integer.MAX_VALUE;
                for(int i = 0;i < convertMethods.size();i++) {
                    Method m = (Method) convertMethods.get(i);
                    Class [] methodPars = m.getParameterTypes();
                    int newconv = _computeWideConvertion(parameters, methodPars); 
                    if(newconv < conv) {
                        conv = newconv;
                        method = m;
                    }
                }    
                /*Class [] p = method.getParameterTypes();
                System.out.println("method " + method.getName());
                for(int i = 0;i < p.length;i++) {
                    System.out.print(p[i].getName() + " ");   
                }
                System.out.print("\n"); */
            }
        }

         // Se for uma classe interna, considera os membros da classe na
        // qual esta declarada
        if(method == null) {
            Class decl = c.getDeclaringClass();
            if(decl != null) {
                method = Util.getMethod(decl, methodName, parameters);
            }
        }

        // deu algum pau
        if(method == null) {
            System.out.println("### Util.getMethod ERROR! ###");
            System.out.println("class = " + c.getName() + " method = " + 
                methodName + " parameters:");
            for(int i = 0;i < parameters.size();i++) {
                Class p = (Class) parameters.get(i);
                System.out.print(" " + p.getName());
            }
            System.out.print("\n");
        }
        return method;
    }
    
    public static Constructor getConstructor(Class c, LinkedList parameters) {
        Constructor cons = null;
        boolean loaded = false;
        try {
            cons = c.getConstructor((Class[]) parameters.toArray());
            loaded = true;
        }
        catch(Exception ex) {
        }

        LinkedList convertMethods = new LinkedList();
        boolean wideConvert = false;
        if(!loaded) {
            Constructor [] constructors = c.getDeclaredConstructors();
            int i = 0;            
            for(;i < constructors.length;i++) {
                Class [] methodPars = constructors[i].getParameterTypes();
                if(methodPars.length != parameters.size()) continue;
                int j = 0;
                for(;j < methodPars.length;j++) {
                    Class pa = (Class) parameters.get(j);
                    String pna = pa.getName();
                    if(!methodPars[j].getName().equals(pna)
                          && !Util.isSubClass(pa, methodPars[j])
                          && !Util.Implements(pa, methodPars[j])
                          && pa != void.class) {                                                                                    
                        
                        if(Util.canWideConvert(pa, methodPars[j])) {                                    
                            wideConvert = true;
                        }
                        else {
                            break;
                        }
                    }
                }
                if(j == methodPars.length) {
                    if(wideConvert == false) {                                                        
                        loaded = true;
                        break;
                    }
                    else {
                        convertMethods.add(constructors[i]);
                    }
                    wideConvert = false;
                }                
            }
            if(loaded) cons = constructors[i];
        }
        
        if(cons == null) {
            if(convertMethods.size() > 0) {
                int conv = Integer.MAX_VALUE;
                for(int i = 0;i < convertMethods.size();i++) {
                    Constructor m = (Constructor) convertMethods.get(i);
                    Class [] methodPars = m.getParameterTypes();
                    int newconv = _computeWideConvertion(parameters, methodPars);
                    if(newconv < conv) {
                        conv = newconv;
                        cons = m;
                    }
                } 
            }                                    
        }
        
        // deu algum pau
        if(cons == null) {
            System.out.println("### Util.getConstructor ERROR! ###");
            System.out.println("class = " + c.getName() + " constructor! " + 
                " parameters:");
            for(int i = 0;i < parameters.size();i++) {
                Class p = (Class) parameters.get(i);
                System.out.print(" " + p.getName());
            }
        }

        return cons;
    }
    
    /* Verifico se e possivel fazer uma widening convertion entre dois
    tipos primitivos */
    public static boolean canWideConvert(Class from, Class to) {                
        if(from == byte.class) {
            if(to == int.class || to == long.class || to == float.class ||
               to == double.class || to == short.class) {
                return true;      
            }
        }
        else        
        if(from == char.class || from == short.class) {
            if(to == int.class || to == long.class || to == float.class ||
               to == double.class) {
                return true;      
            }
        }
        else
        if(from == int.class) {
            if(to == long.class || to == float.class || to == double.class) {
                return true;  
            }
        }
        else
        if(from == long.class) {
            if(to == float.class || to == double.class) {
                return true;   
            }
        }
        else
        if(from == float.class) {
            if(to == double.class) {
                return true;  
            }
        }        
        return false;
    }    
         
    public static boolean isSubClass(Class subType, Class type) {
        // Isso vale para arrays tambem
        if(type == Object.class) {
            if(!isPrimitive(subType)) return true;
        }

        while(type.isArray()) {
           type = type.getComponentType();
        }
        while(subType.isArray()) {
            subType = subType.getComponentType();
        }
        Class t = subType;
        while(t != null) {
            if(t == type) return true;
            t = t.getSuperclass();
        }
        return false;
    }
    
    public static boolean isSubInterface(Class subInterface, Class inter) {
        Class t = subInterface;
        while(t != null) {
            if(t == inter) return true;
            Class [] sub = t.getInterfaces(); 
            for(int i = 0;i < sub.length;i++) {
                if(isSubInterface(sub[i], inter)) return true;
            }
            t = t.getSuperclass();
        }
        return false;
    }
    
    // Determina se a classe implmenta a interface
    public static boolean Implements(Class cl, Class inter) {
        // Testa para a classe e suas subclasses
        Class t = cl;
        while(t != null) {
            Class [] imp = t.getInterfaces();
            for(int i = 0;i < imp.length;i++) {
                // Verifica se a classe implementa diretamente a interface
                if(imp[i] == inter) return true;

                // Verifica se a classe implementa diretamente uma superinterface
                if(isSubInterface(imp[i], inter)) return true;            
            }
            t = t.getSuperclass();
        }
        return false;
    }

    public static boolean isPrimitive(Class type) {
        if(type == int.class ||
           type == void.class ||
           type == long.class ||
           type == double.class ||
           type == float.class ||
           type == char.class ||
           type == byte.class ||
           type == boolean.class ||
           type == short.class) return true;

       return false;
    }

    /** Retorno a classe de um dado nome. 
        ClassName e package indicam o nome de uma classe e pacote onde o tipo
        pode ter sido definido.
        Casos:
          * primitivo
          * nome completo
          * so o ultimo nome do tipo
            * mesmo pacote
            * import
              * completa um .*
              * nome completo no import
          * classe interna
            * um nome : pertece a ClassName e PackName
            * mais de um nome
              * import
    **/
    // TEM PAU ESSE METODO....ATOR CLOCK....
    public static Class getType(String typeName, String className, String packName, 
             LinkedList imports) {
        if(typeName == null) return void.class;

        // Verifica os tipos primitivos
        if(typeName.equals("int")) {
            return int.class;
        }
        else
        if(typeName.equals("void") || typeName.equals("null")) {
            return void.class;
        }
        else
        if(typeName.equals("double")) {
            return double.class;
        }
        else
        if(typeName.equals("float")) {
            return float.class;
        }
        else
        if(typeName.equals("boolean")) {
            return boolean.class;
        }
        else
        if(typeName.equals("byte")) {
            return byte.class;
        }
        else
        if(typeName.equals("char")) {
            return char.class;
        }
        else
        if(typeName.equals("short")) {
            return short.class;
        }
        else
        if(typeName.equals("long")) {
            return long.class;
        }

        ClassLoader loader = ClassLoader.getSystemClassLoader();
        Class ret = null;

        // Adiciona o nome do pacote
        if(packName != null) {
            String name = new String(packName + "." + typeName);
            try {
                ret = loader.loadClass(name);
            }
            catch(ClassNotFoundException ex) {
            }
        }
        if(ret != null) return ret;

        // Verifica se o nome do tipo esta completo
        try {
            ret = loader.loadClass(typeName);
        }
        catch(ClassNotFoundException ex) {
        }
        catch(NoClassDefFoundError er) { }
        if(ret != null) return ret;
                 
        // Verifica se e uma classe interna
        if(className != null) {
            if(packName != null) {
                String name = new String(packName + "." + className + "$" +
                    typeName);
                try {
                    ret = loader.loadClass(name);
                }
                catch(ClassNotFoundException ex) {
                }
                if(ret != null) return ret;
                
                // local ...                
                name = new String(packName + "." + className + "$1$" + 
                    typeName);
                try {
                    ret = loader.loadClass(name);
                }
                catch(ClassNotFoundException ex) {
                }
            }
            if(ret != null) return ret;
            
            String name = new String(className + "$" + typeName);
            try {
                ret = loader.loadClass(name);
            }
            catch(ClassNotFoundException ex) {
            }
            if(ret != null) return ret;
            
            name = new String(className + "$1$" + typeName);
            try {
                ret = loader.loadClass(name);
            }
            catch(ClassNotFoundException ex) {
            }
        }

        // Verifica se e uma classe da linguagem basica
        if(ret == null) {
            try {
                ret = loader.loadClass("java.lang." + typeName);
            }
            catch(ClassNotFoundException ex) {
            }
            if(ret != null) return ret;
        }
/*
        System.out.println("----------------");
        System.out.println("TYPENAME = " + typeName);
        System.out.println("Testando imports..."); */
        
        // Verifica se e uma classe de algum import
        if(ret == null) {
            for(int i = 0;i < imports.size();i++) {
                String imp = (String) imports.get(i);
                String t = new String(imp + typeName);

                //System.out.println("Testando ... import + typename :  " + t);
                try {
                    ret = loader.loadClass(t);
                }
                catch(ClassNotFoundException ex) {
                }
                if(ret != null) {
                    return ret;
                }

                // Verifica se o tipo e uma classe interna e statica
                try {
                    ret = loader.loadClass(innerClassLoader(t));
                }
                catch(ClassNotFoundException ex) {
                }
                if(ret != null) {
                    return ret;
                }

                // Verifica se a classe esta no import com o nome completo
                try {
                    ret = loader.loadClass(imp);
                }
                catch(ClassNotFoundException ex) {
                }
                if(ret != null) {

                    //System.out.println("tstando import cru " + ret.getName());

                    // pega o ultimo nome da classe
                    StringTokenizer st = new StringTokenizer(ret.getName(), ".");
                    String tk = null;
                    while(st.hasMoreTokens()) tk = st.nextToken();
                    String lastName = tk;
                    if(lastName.equals(typeName)) {
                        return ret;
                    }
                    // Verifica se e uma classe interna da classe
                    // especificada explicitamente no import

                    Class [] ci = ret.getClasses();
                    for(int k = 0;k < ci.length;k++) {
                        // pega o ultimo nome. Como e interna, vai tar
                        // nome$nome
                        st = new StringTokenizer(ci[k].getName(), ".");
                        while(st.hasMoreTokens()) tk = st.nextToken();
                        if(tk.equals(typeName)) {
                            return ci[k];
                        }
                        if(innerClassName(tk).equals(typeName)) {
                            return ci[k];
                        }
                    }
                    
                    ci = ret.getInterfaces();
                    for(int k = 0;k < ci.length;k++) {
                        st = new StringTokenizer(ci[k].getName(), ".");
                        while(st.hasMoreTokens()) tk = st.nextToken();
                        if(tk.equals(typeName)) {
                            return ci[k];
                        }
                        if(innerClassName(tk).equals(typeName)) {
                            return ci[k];
                        }
                    }
                    
                    ret = null;
                }
            }
        }
/*
        if(ret == null) {
            System.out.println("Util.getType() Problem!: package = " + 
                packName + " type = " + typeName + " className = " +
                className);            
        }
*/
        return ret;
    }

    public static boolean isSubClass(String name, LinkedList imports, 
            String superclass) {
        try {
            ClassLoader cl = ClassLoader.getSystemClassLoader();
            Class c = cl.loadClass(name);
                
            while(c != null && !c.getName().equals("Object")) {
                if(c.getName().equals(superclass)) {
                    return true;   
                }
                c = c.getSuperclass();
            }
        }
        catch(ClassNotFoundException ex) {
        
        }
        catch(NoClassDefFoundError er) { }
        
        for(int i = 0;i < imports.size();i++) {
            String s = (String) imports.get(i);
            String cname = new String(s + name);
            try {
                ClassLoader cl = ClassLoader.getSystemClassLoader();
                Class c = cl.loadClass(cname);
                
                while(c != null && !c.getName().equals("Object")) {
                   if(c.getName().equals(superclass)) {
                       return true;   
                   }
                   c = c.getSuperclass();
                }
            }
            catch(ClassNotFoundException ex) {

            }

            StringTokenizer st = new StringTokenizer(s,".");
            int count = st.countTokens();
            for(int j = 0;j < count - 1;j++) st.nextToken();
            cname = st.nextToken();
            
            if(cname.equals(name)) {
                try {
                    ClassLoader cl = ClassLoader.getSystemClassLoader();
                    Class c = cl.loadClass(s);
                
                    while(c != null && !c.getName().equals("Object")) {
                        if(c.getName().equals(superclass)) {
                           return true;   
                        }
                        c = c.getSuperclass();
                    }                                                
                }
                catch(ClassNotFoundException ex) {

                }
            }
        }
        return false;
    }

    public static String absolutePath(String className) {
        StringBuffer buf;
        try {
            ClassLoader cl = ClassLoader.getSystemClassLoader();
            Class c = cl.loadClass(className);
        
            java.security.ProtectionDomain d = c.getProtectionDomain();
            java.security.CodeSource cs = d.getCodeSource();
            java.net.URL url = cs.getLocation();
            
            buf = new StringBuffer(url.getFile());
            
            String s;
            StringTokenizer st = new StringTokenizer(className, ".");
            while(st.hasMoreTokens()) {
                if(st.countTokens() == 1) break;
                s = (String) st.nextToken();
                buf.append(s + "/");
            }
            s = st.nextToken();
            buf.append(s + ".java");
        }
        catch(ClassNotFoundException ex) {
            System.out.println("Util.absolutePath(): " + ex);
            return null;
        }
        return buf.toString();
    }

    public static AST inorderTraverse(AST root, TraverseCall tc) {
        AST v = root.getFirstChild();
        AST s;
        if(v != null) {
            s = inorderTraverse(v, tc);    
        }
        else {
            tc.callBack(root);
            return root.getNextSibling();
        }
        tc.callBack(root);
        tc.callBack(s);

        return root.getNextSibling();
    }

    public static AST parseFile(String fname) {
        Util u = new Util();
        return u._parseFile(fname);
    }

    public static String getClassName(AST root) {
        boolean haspkg = false;
        AST v = root;       
        StringBuffer className = new StringBuffer();                
        if(v.getType() == JavaTokenTypes.PACKAGE_DEF) {
            NameCollector nc = new NameCollector();
            inorderTraverse(v.getFirstChild(), nc);
            className.append(nc.getName());
            haspkg = true;
        }

        v = root;
        int type = v.getType();        
        while(v != null && type != JavaTokenTypes.CLASS_DEF) {
            v = v.getNextSibling();
            type = v.getType();                        
        }                
        v = v.getFirstChild();
        v = v.getNextSibling();                                
        
        if(haspkg) {
            className.append("." + v.getText());
        }
        else {
            className.append(v.getText());
        }
        return className.toString();
    }
    
    public static String isAtomicActor(String fname) {
        AST root = Util.parseFile(fname);

        boolean haspkg = false;
        AST v = root;       
        StringBuffer className = new StringBuffer();
        if(v.getType() == JavaTokenTypes.PACKAGE_DEF) {
            NameCollector nc = new NameCollector();
            Util.inorderTraverse(v.getFirstChild(), nc);
            className.append(nc.getName());
            haspkg = true;
        }
        v = root;                
        while(v != null && v.getType() != JavaTokenTypes.CLASS_DEF) {
            v = v.getNextSibling();            
        }                
        if(v == null) return null;

        v = v.getFirstChild();
        v = v.getNextSibling();
 
        if(haspkg) {
            className.append("." + v.getText());
        }
        else {
            className.append(v.getText());
        }

        if(v.getText().equals("Object")) return null;
        if(v.getText().equals("TypedAtomicActor") ||
           v.getText().equals("AtomicActor")) return className.toString();
        
        v = v.getNextSibling();
        NameCollector nc = new NameCollector();
        Util.inorderTraverse(v.getFirstChild(), nc);                
        
        if(nc.getName().equals("AtomicActor") ||
               nc.getName().equals("TypedAtomicActor")) {                
           return className.toString(); 
        }                
        try {
            ClassLoader cl = ClassLoader.getSystemClassLoader();
            Class c = cl.loadClass(className.toString());
            
            Class sup = c.getSuperclass();
            String path = Util.absolutePath(sup.getName());

            String n = isAtomicActor(path);
            if(n == null) return null;
            return className.toString();             
        }
        catch(ClassNotFoundException ex) {
            System.out.println(ex);   
        }
        return null;
    }
    
    ///////////////////////////////////////////////////////////////////
    ////                        private methods                    ////
        
    private static int _computeWideConvertion(LinkedList par, Class [] mpar) {
        int res = 0;     
        for(int i = 0;i < par.size();i++) {
            Class from = (Class) par.get(i);
            Class to = mpar[i];
            if(from == byte.class) {
                if(to == short.class) {
                    res += 1;   
                }
                else
                if(to == int.class) {
                    res += 2;   
                }
                else
                if(to == long.class) {
                    res += 3;   
                }
                else
                if(to == float.class) {
                    res += 4;   
                }
                else
                if(to == double.class) {
                    res += 5;
                }                      
            }
            else        
            if(from == char.class || from == short.class) {                
                if(to == int.class) {
                    res += 1;   
                }
                else
                if(to == long.class) {
                    res += 2;   
                }
                else
                if(to == float.class) {
                    res += 3;   
                }
                else
                if(to == double.class) {
                    res += 4;
                }                        
            }
            else
            if(from == int.class) {                
                if(to == long.class) {
                    res += 1;   
                }
                else
                if(to == float.class) {
                    res += 2;   
                }
                else
                if(to == double.class) {
                    res += 3;
                }            
            }
            else
            if(from == long.class) {                
                if(to == float.class) {
                    res += 1;   
                }
                else
                if(to == double.class) {
                    res += 2;
                }                        
            }
            else
            if(from == float.class) {
                if(to == double.class) {
                    res += 1;
                }
            }
        }
        return res;
    }
    
    private static void _copyTree(LinkedAST current, AST old) {
        current.setType(old.getType());
        current.setText(old.getText());
        current.setParent(null);

        LinkedAST sub = _copySubTree(old.getFirstChild());
        if(sub != null) {
            current.setFirstChild(sub);
            sub.setParent(current);
        }
        LinkedAST parent = current;
        AST v = old.getNextSibling();
        while(v != null) {
            current = new LinkedAST();
            current.setType(v.getType());
            current.setText(v.getText());
            current.setParent(parent);
            parent.setNextSibling(current);

            sub = _copySubTree(v.getFirstChild());
            if(sub != null) {
                current.setFirstChild(sub);
                sub.setParent(current);
            }
            parent = current;
            
            v = v.getNextSibling();
        }
    }

    private static LinkedAST _copySubTree(AST v) {
        if(v == null) return null;

        LinkedAST ret = new LinkedAST();
        _copyTree(ret, v);

        return ret;
    }
    
    private AST _parseFile(String fname) {
        AST root = null;
        try {
            File f = new File(fname);
	    BufferedReader bf = new BufferedReader(new FileReader(f));

	    JavaLexer lexer = new JavaLexer(bf);
	    lexer.setFilename(fname);

	    JavaRecognizer parser = new JavaRecognizer(lexer);
	    parser.setFilename(fname);

	    parser.compilationUnit();
            root = parser.getAST();
        }
        catch(java.io.FileNotFoundException ex) {
            System.out.println(ex);
        }
        catch(antlr.RecognitionException ex) {
            System.out.println(ex);
        }
        catch(antlr.TokenStreamException ex) {
            System.out.println(ex);
        }
        return root;
    }
    
}
